using Sustainalytics.ESGRatings.BusinessLogic.Computing.Exceptions;
using Sustainalytics.ESGRatings.Computing.Entities;
using System;
using System.Collections.Generic;
using System.Linq;
using Serilog;

namespace Sustainalytics.ESGRatings.BusinessLogic.Computing
{
    internal abstract class ComputationSet : IComputationSet
    {
        public IndicatorsConfiguration IndicatorsConfiguration;

        public Dictionary<Guid, WeightMatrixStruct> Matrices = new Dictionary<Guid, WeightMatrixStruct>();

        public Dictionary<int, IIndex<Guid, string>> ComparativeGroups = new Dictionary<int, IIndex<Guid, string>>();

        protected ComputationSet(int companiesCount, IndicatorsConfiguration indicatorsConfiguration)
        {
            IndicatorsNo = indicatorsConfiguration.Count;
            CompaniesNo = companiesCount;
            IndicatorsConfiguration = indicatorsConfiguration;
        }

        public int CompaniesNo { get; private set; }
        public Guid? DefaultMatrixId { get; set; }
        public int Id { get; protected set; }
        public int IndicatorsNo { get; private set; }
        public bool IsActive { get; set; }

        public ComputationSetTypeEnum SetType { get; set; }
        public int CurrentId { get; set; }

        public Dictionary<Guid, IndicatorDataStruct[]> IndicatorScores { get; set; }

        /// <exception cref="ArgumentNullException"><paramref name="indicatorsData"/> is <see langword="null" />.</exception>
        /// <exception cref="UnknownCompanyException">Company not found in comparative group.</exception>
        /// <exception cref="UnknownIndicatorException">Indicator code was not found in templates set.</exception>
        public void AddIndicatorsRawScore(IEnumerable<EntityIndicators<Guid, IndicatorScore>> indicatorsData,
            int comparativeGroupIndex = 0)
        {
            var companyIndicators = indicatorsData as EntityIndicators<Guid, IndicatorScore>[] ?? indicatorsData.ToArray();

            var newScores = new Dictionary<Guid, IndicatorDataStruct[]>(CompaniesNo);

            IIndex<Guid, string> comparativeGroup;

            if (!ComparativeGroups.TryGetValue(comparativeGroupIndex, out comparativeGroup))
            {
                throw new ESGEntityNotFound("Comparative groups empty");
            }

            var rawScores = new float[CompaniesNo * IndicatorsNo]; // 0 initialized

            foreach (var indicatorData in companyIndicators)
            {
                IndicatorDataStruct[] array = new IndicatorDataStruct[IndicatorsConfiguration.Count];

                Guid companyId = indicatorData.EntityId;

                int companyIndex = comparativeGroup.GetIndex(companyId);

                if (companyIndex < 0)
                {
                    throw new UnknownCompanyException(companyId);
                }

                newScores.Add(indicatorData.EntityId, array);

                foreach (var data in indicatorData.IndicatorsData)
                {
                    var index = IndicatorsConfiguration.GetIndex(data.Code);

                    IndicatorDataStruct.SetNotNaNValue(ref array[index], data.Value.RawScore, data.Value.Weight, data.Value.WeightedScore);

                    var cIndex = GetColumnOrderIndex(companyIndex, index, CompaniesNo);
                    rawScores[cIndex] = data.Value.RawScore;
                }
            }

            IndicatorScores = newScores;

            Log.Debug("Indicators build: {0} indicators", IndicatorScores.Count);

        }

        public bool SetHasDefaultValues()
        {
            throw new NotImplementedException();
        }

        public abstract void AddMatrix<TV>(Guid matrixId, IEnumerable<EntityIndicators<TV, float>> weights);


        public bool DeleteMatrix(Guid matrixId)
        {

            if (matrixId == DefaultMatrixId.GetValueOrDefault())
            {
                return false;
            }

            return true;
        }

        /// <exception cref="ArgumentOutOfRangeException">comparativeGroupIndex</exception>
        public IEnumerable<Guid> GetCompaniesInGroup(Guid companyId, int comparativeGroupIndex = 0)
        {
            IIndex<Guid, string> comparativeGroup;

            if (!ComparativeGroups.TryGetValue(comparativeGroupIndex, out comparativeGroup))
            {
                throw new ArgumentOutOfRangeException(nameof(comparativeGroupIndex));
            }

            return comparativeGroup.GetGroupElements(companyId);
        }

        public IEnumerable<Guid> GetLoadedMatrices()
        {
            var result = new List<Guid>(Matrices.Keys);
            if (DefaultMatrixId != null)
            {
                result.Add(DefaultMatrixId.Value);
            }
            return result;
        }

        protected int GetColumnOrderIndex(int rowIndex, int columnIndex, int rows/*, int columns*/)
        {
            return columnIndex * rows + rowIndex;
        }

        protected int GetCompanyColumnOrderIndex(int rowIndex, int columnIndex, int rows)
        {
            return rowIndex * rows + columnIndex;
        }

        protected int NextId()
        {
            return ++CurrentId;
        }
    }
}